package com.wiscamp.ninechapters.problems.infrastructure.repositories;

import com.wiscamp.ninechapters.problems.domain.models.collections.CollectionProblem;
import com.wiscamp.ninechapters.problems.domain.models.collections.ProblemCollection;
import com.wiscamp.ninechapters.problems.domain.repositories.ProblemCollectionRepository;
import com.wiscamp.ninechapters.problems.infrastructure.converters.ProblemCollectionConverter;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.collections.CollectionProblemMapper;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.collections.ProblemCollectionMapper;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.collections.ProblemCollectionViewMapper;
import com.wiscamp.ninechapters.problems.infrastructure.po.collections.CollectionProblemPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.collections.ProblemCollectionPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.collections.ProblemCollectionView;
import cube.ddd.aspect.HandleDataException;
import cube.ddd.common.query.PagedObjects;
import cube.ddd.common.query.PagedQuery;
import cube.ddd.domain.BaseRepository;
import cube.ddd.utils.PersistenceUtils;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

@Repository
public class ProblemCollectionRepositoryImpl extends BaseRepository implements ProblemCollectionRepository {
    private final ProblemCollectionMapper problemCollectionMapper;

    private final ProblemCollectionViewMapper problemCollectionViewMapper;

    private final CollectionProblemMapper collectionProblemMapper;

    public ProblemCollectionRepositoryImpl(ProblemCollectionMapper problemCollectionMapper, ProblemCollectionViewMapper problemCollectionViewMapper, CollectionProblemMapper collectionProblemMapper) {
        this.problemCollectionMapper = problemCollectionMapper;
        this.problemCollectionViewMapper = problemCollectionViewMapper;
        this.collectionProblemMapper = collectionProblemMapper;
    }

    // region problemCollection
    private void loadDetails(ProblemCollection collection) {
        if (Objects.nonNull(collection)) {
            var collectionProblems = getCollectionProblems(collection.getCollectionId());
            collection.setCollectionProblems(collectionProblems);
        }
    }

    private ProblemCollectionPO getProblemCollectionPO(long collectionId) {
        return PersistenceUtils.findById(collectionId, problemCollectionMapper::findById);
    }

    @HandleDataException
    public Collection<Long> getProblemIdsByCollection(long collectionId) {
        return PersistenceUtils.getListById(collectionId, collectionProblemMapper::findProblemIdsByCollection);
    }

    @HandleDataException
    public ProblemCollection getProblemCollection(long collectionId) {
        var collection = PersistenceUtils.findEntityById(collectionId, problemCollectionViewMapper::findById,
                ProblemCollectionConverter.Instance::toProblemCollectionByView);
        loadDetails(collection);
        return collection;
    }

    /**
     * Gets a problem collection
     *
     * @param collectionCode
     * @param organizationId
     * @return
     */
    @HandleDataException
    public ProblemCollection getProblemCollectionByCode(@NotBlank String collectionCode, @Min(0) long organizationId) {
        var collection = PersistenceUtils.getEntityByTextAndId(collectionCode, organizationId, problemCollectionViewMapper::findByCollectionCode,
                ProblemCollectionConverter.Instance::toProblemCollectionByView);
        loadDetails(collection);
        return collection;
    }

    /**
     * Gets problem collections
     *
     * @param collectionName
     * @param organizationId
     * @return
     */
    @HandleDataException
    public Collection<ProblemCollection> getProblemCollectionsByName(@NotBlank String collectionName, @Min(0) long organizationId) {
        var collections = PersistenceUtils.getEntitiesByTextAndId(collectionName, organizationId, problemCollectionViewMapper::findListByCollectionName,
                ProblemCollectionConverter.Instance::toProblemCollectionByView);
        for (var collection : collections) {
            loadDetails(collection);
        }
        return collections;
    }

    /**
     * Gets problem collections by name prefix
     *
     * @param name
     * @param organizationId
     * @return
     */
    @HandleDataException
    public Collection<ProblemCollection> getProblemCollectionsByNameContains(@NotBlank String name, @Min(0) long organizationId) {
        var collections = PersistenceUtils.getEntitiesByTextAndId(name, organizationId, problemCollectionViewMapper::findListByNameContains,
                ProblemCollectionConverter.Instance::toProblemCollectionByView);
        for (var collection : collections) {
            loadDetails(collection);
        }
        return collections;
    }

    @HandleDataException
    public Collection<ProblemCollection> getProblemCollectionsByNameContains(@NotBlank String name, @Min(0) long organizationId, @NotBlank String gradeLevel) {
        var collections = getProblemCollectionsByNameContains(name, organizationId);
        return collections.stream().filter(c -> c.getCollectionName().contains(gradeLevel)).collect(Collectors.toList());
    }


    /**
     * Gets problem collections
     *
     * @param organizationId
     * @return
     */
    @HandleDataException
    public Collection<ProblemCollection> getProblemCollectionsByOrganization(@Min(0) long organizationId) {
        var collections = PersistenceUtils.getEntitiesById(organizationId, problemCollectionViewMapper::findListByOrganization,
                ProblemCollectionConverter.Instance::toProblemCollectionByView);
        for (var collection : collections) {
            loadDetails(collection);
        }
        return collections;
    }

    @HandleDataException
    public Collection<ProblemCollection> getProblemCollectionsByExam(@Min(0) long examId) {
        var collections = PersistenceUtils.getEntitiesById(examId, problemCollectionViewMapper::findListByExam,
                ProblemCollectionConverter.Instance::toProblemCollectionByView);
        for (var collection : collections) {
            loadDetails(collection);
        }
        return collections;
    }

    /**
     * Gets problem collections by pager
     *
     * @param query
     * @return
     */
    @HandleDataException
    public PagedObjects<ProblemCollection> getProblemCollectionsByQuery(@NotNull PagedQuery query) {
        PagedObjects<ProblemCollectionView> pagedObjects = PersistenceUtils.getPersistenceObjectsByQuery(query, problemCollectionViewMapper::findAll);
        return PersistenceUtils.convertPagedObjects(pagedObjects, ProblemCollectionConverter.Instance::toProblemCollectionByView);
    }

    public long getExamIdByCollection(long collectionId) {
        var collection = getProblemCollection(collectionId);
        if (Objects.isNull(collection)) return 0;
        return collection.getExamId();
    }

    /**
     * Creates a problem collection
     *
     * @param collection
     * @return
     */
    @HandleDataException
    public ProblemCollection saveProblemCollection(@NotNull ProblemCollection collection) {
        var id = collection.getCollectionId();
        if (collection.getCollectionId() <= 0) {
            var newCollection = PersistenceUtils.createEntity(collection, problemCollectionMapper::saveAndFlush,
                    ProblemCollectionConverter.Instance::toProblemCollectionPO,
                    ProblemCollectionConverter.Instance::toProblemCollection);
            id = newCollection.getCollectionId();
        } else {
            var existedPO = getProblemCollectionPO(collection.getCollectionId());
            if (existedPO == null) existedPO = new ProblemCollectionPO();
            PersistenceUtils.updateEntity(collection, existedPO, problemCollectionMapper::saveAndFlush,
                    ProblemCollectionConverter.Instance::toProblemCollectionPO,
                    ProblemCollectionConverter.Instance::toProblemCollection);
        }

        return getProblemCollection(id);
    }
    // endregion

    // region collectionProblem
    private CollectionProblemPO getCollectionProblemPO(@Min(0) long id) {
        return PersistenceUtils.findById(id, collectionProblemMapper::findById);
    }

    @HandleDataException
    public CollectionProblem getCollectionProblem(@Min(0) long id) {
        return PersistenceUtils.findEntityById(id, collectionProblemMapper::findById,
                ProblemCollectionConverter.Instance::toCollectionProblem);
    }

    @HandleDataException
    public CollectionProblem getCollectionProblemByProblem(@Min(0) long problemId) {
        return PersistenceUtils.getEntityById(problemId, collectionProblemMapper::findByProblem,
                ProblemCollectionConverter.Instance::toCollectionProblem);
    }

    @HandleDataException
    public CollectionProblem getCollectionProblemByProblem(@Min(0) long collectionId, @Min(0) long problemId) {
        return PersistenceUtils.getEntityByIdAndId(collectionId, problemId, collectionProblemMapper::findByProblem,
                ProblemCollectionConverter.Instance::toCollectionProblem);
    }

    @HandleDataException
    public Collection<CollectionProblem> getCollectionProblems(long collectionId) {
        return PersistenceUtils.getEntitiesById(collectionId, collectionProblemMapper::findListByCollection,
                ProblemCollectionConverter.Instance::toCollectionProblem);
    }

    /**
     * Updates a problem collection
     *
     * @param pool
     * @return
     */
    @HandleDataException
    public CollectionProblem saveCollectionProblem(@NotNull CollectionProblem collectionProblem) {
        var id = collectionProblem.getId();
        if (collectionProblem.getId() <= 0) {
            var newCollection = PersistenceUtils.createEntity(collectionProblem, collectionProblemMapper::saveAndFlush,
                    ProblemCollectionConverter.Instance::toCollectionProblemPO,
                    ProblemCollectionConverter.Instance::toCollectionProblem);
            id = newCollection.getId();
        } else {
            var existedPO = getCollectionProblemPO(collectionProblem.getId());
            if (existedPO == null) existedPO = new CollectionProblemPO();
            PersistenceUtils.updateEntity(collectionProblem, existedPO, collectionProblemMapper::saveAndFlush,
                    ProblemCollectionConverter.Instance::toCollectionProblemPO,
                    ProblemCollectionConverter.Instance::toCollectionProblem);
        }

        return getCollectionProblem(id);
    }

    @HandleDataException
    public boolean deleteCollectionProblem(@Min(0) long id) {
        return PersistenceUtils.delete(id, collectionProblemMapper::deleteById);
    }

    /**
     * Updates the quantity of a problem collection
     *
     * @param collectionId
     */
    @HandleDataException
    public boolean updateProblemQuantity(@Min(0) long collectionId) {
        problemCollectionMapper.updateProblemQuantity(collectionId);
        return true;
    }

    /**
     * Gets maximum item index of a problem collection
     *
     * @param collectionId
     * @return
     */
    @HandleDataException
    public int getMaxProblemIndex(@Min(0) long collectionId) {
        var index = collectionProblemMapper.findMaxProblemIndex(collectionId);
        if (index != null) {
            return index;
        }
        return 0;
    }

    /**
     * Gets a problem id by index
     *
     * @param collectionId
     * @param index
     * @return
     */
    @HandleDataException
    public long getProblemIdByIndex(@Min(0) long collectionId, @Min(0) int index) {
        var collectionProblem = getCollectionProblemByIndex(collectionId, index);
        if (Objects.nonNull(collectionProblem))
            return collectionProblem.getProblemId();
        return 0;
    }

    @HandleDataException
    public CollectionProblem getCollectionProblemByIndex(long collectionId, int index) {
        var collectionProblemPO = collectionProblemMapper.findByIndex(collectionId, index);
        return ProblemCollectionConverter.Instance.toCollectionProblem(collectionProblemPO);
    }

    @HandleDataException
    public boolean resortProblemIndex(long collectionId, int index) {
        collectionProblemMapper.resortProblemIndex(collectionId, index);
        return true;
    }

    // endregion
}
